﻿package  {
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.events.Event;
import flash.display.BitmapData
import flash.events.KeyboardEvent
import flash.events.MouseEvent
import flash.utils.Dictionary
import org.papervision3d.core.proto.GeometryObject3D
import org.papervision3d.core.geom.TriangleMesh3D
import org.papervision3d.core.geom.renderables.Triangle3D;
import org.papervision3d.core.geom.renderables.Vertex3D;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.materials.BitmapMaterial
import org.papervision3d.materials.BitmapFileMaterial;
import org.papervision3d.materials.MovieMaterial;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.core.math.Number3D
import org.papervision3d.objects.DisplayObject3D
import org.papervision3d.view.BasicView;
import org.papervision3d.view.stats.StatsView
import physics.PolygonPhysics
import physics.Number3DPlus;
/**
* ...
* @author Exey Panteleev
*/
[ SWF( backgroundColor = '#000000', width = '800', height = '600', frameRate='60' ) ]
public class Main extends BasicView {
	
	//Bullet Resistant Glass by Mawhrin Skel http://flickr.com/photos/24789570@N03/2698809741/
	[Embed(source='2698809741_76eddd085c.jpg')]
    private var MeshMat:Class;
	
	[Embed(source = 'broken.png')]
	private var BrokenCenter:Class
	
	//Parquet 2.0 version 6 by Kochubey http://www.filterforge.com/filters/488.html
	private const GROUND_TEXTURE:String = "488-v6+.jpg"
	
	private const BULLET_TEXTURE:String = "bullet_back.png"
	
	private static const BOUNDS:Number = 3000; // Limit for bullet
	private static const MESH_SIZE:uint = 512
	private static const MESH_TESSELATION_X:uint = 8
	private static const MESH_TESSELATION_Y:uint = 8
	private static const GROUND_Y:int = -300
	private static const SHOOT_POWER:Number = 0.15 // 1 is maximum
	
	//Glass Mesh
	private var mesh:Plane;
	private var meshMaterial:MaterialObject3D
	private var meshSprite:Sprite
	private var brokenCenterBmp:Bitmap
	
	//Bullet
	private var bullets:Array = new Array();
	private var collidedBulletIndex:uint;
	
	//Physics
	private var isPhysicsOn:Boolean = false
	private var meshPartsPhysics:Array = new Array()
	private var detainedParts:Array = new Array()
	private var detainedPartsCounter:uint = 0
	
	private var ground:Plane
	
	public function Main() {
		super(800, 600, false, false, "CAMERA3D");
		preparePv3d();
		createMaterials();
		createObjects();
		addListeners();
	}
	
	public function preparePv3d():void {
		addChild(new StatsView(renderer));
	}
	
	public function createMaterials():void {
		meshSprite = new Sprite()
		meshSprite.addChild(new MeshMat() as Bitmap)
		meshSprite.alpha = 0.3
		brokenCenterBmp = new BrokenCenter() as Bitmap
		brokenCenterBmp.alpha = 0
		meshSprite.addChild(brokenCenterBmp);
		meshMaterial = new MovieMaterial(meshSprite, true, true, false, new Rectangle(0, 0, MESH_SIZE, MESH_SIZE))
		meshMaterial.doubleSided = true;
	}
	public function createObjects():void {
		ground = new Plane(new BitmapFileMaterial(GROUND_TEXTURE), 1024, 1024, 6, 6)
		ground.rotationX = -270
		ground.y = GROUND_Y
		ground.z = 100
		scene.addChild(ground)
		restoreMesh() // create glass mesh
	}
	
	public function breakMesh(mesh:TriangleMesh3D):void {
		if (!isPhysicsOn && bullets[collidedBulletIndex] !=null) {
			detainedPartsCounter = 0
			detainedParts = new Array()
			meshPartsPhysics = new Array();
			
			var geom:GeometryObject3D = mesh.geometry;
			var oTris:Array = geom.faces;
			var nTris:Array = new Array();
			var vert:Vertex3D;
			var nVert:Vertex3D;
			var triangle:Triangle3D;
			var nTriangle:Triangle3D;
			
			for (var i:int = 0; i < oTris.length; i++) 
			{
				var currFace:Triangle3D = oTris[i];
				var vertCacheA:Dictionary = new Dictionary(true);
				var triangleBucketA:Array = new Array();
				var vertBucketA:Array = new Array();
				var meshX:int
				var meshY:int
				triangle = currFace
				for (var k:int = 0; k < currFace.vertices.length; k++) 
				{
					vert = currFace.vertices[k];
					if(!(nVert = vertCacheA[vert])){
						nVert = vert.clone();
						vertCacheA[vert] = nVert;
					}
					
					vertBucketA.push(nVert)
					triangle.vertices[k] = nVert;
				}
				// Save position for new mesh 
				meshX = triangle.vertices[0].x
				meshY = triangle.vertices[0].y
				
				// Calculate shift for vertices
				var shiftX:int = MESH_SIZE/MESH_TESSELATION_X
				var shiftY:int = MESH_SIZE/MESH_TESSELATION_Y
				if (triangle.vertices[0].x > triangle.vertices[1].x)
					shiftX = -MESH_SIZE/MESH_TESSELATION_X
				if (triangle.vertices[0].y >  triangle.vertices[2].y)
					shiftY = -MESH_SIZE/MESH_TESSELATION_Y
				
				// Set local position for vertices 
				triangle.vertices[0].x = 0
				triangle.vertices[0].y = 0
				triangle.vertices[1].x = shiftX
				triangle.vertices[1].y = 0
				triangle.vertices[2].x = 0
				triangle.vertices[2].y = shiftY
				triangle.updateVertices();
				
				//create new mesh add it on scene
				var meshA:TriangleMesh3D = new TriangleMesh3D(mesh.material, vertBucketA, [triangle]);
				meshA.geometry.ready = true
				meshA.x = meshX
				meshA.y = meshY
				
				brokenCenterBmp.x = bullets[collidedBulletIndex].x - (mesh.x - MESH_SIZE*0.5) -brokenCenterBmp.width/2
				brokenCenterBmp.y = -bullets[collidedBulletIndex].y - (mesh.y - MESH_SIZE*0.5) -brokenCenterBmp.height/2
				brokenCenterBmp.alpha = 1
				
				//add physics for new mesh
				meshPartsPhysics.push(new PolygonPhysics(TriangleMesh3D(meshA), 10, .5, 1));
				scene.addChild(meshPartsPhysics[i].Object3D);
			}
			
			for each(var meshPart:PolygonPhysics in meshPartsPhysics) 
			{
				var xdiff : Number = meshPart.Object3D.x-bullets[collidedBulletIndex].x; 
				var ydiff : Number = meshPart.Object3D.y-bullets[collidedBulletIndex].y; 
				
				var distance : Number = Math.sqrt((xdiff * xdiff) + (ydiff * ydiff))
				if (distance > MESH_SIZE*SHOOT_POWER*1.5){
					meshPart.isDetained = true;
					//add impulse for detained parts
					meshPart.LinImpulse = new Number3D(Math.random()*10, 10, 10-Math.random()*20)
					meshPart.RotImpulse= new Number3D(Math.random()*.2,Math.random()*.2,Math.random()*.2)
				}else {	
					//add impulse for not detained parts
					var impX:int
					var impY:int
					if (bullets[collidedBulletIndex].x < meshPart.Object3D.x)
						impX = MESH_SIZE*SHOOT_POWER*.5
					else
						impX = -MESH_SIZE*SHOOT_POWER*.5
						
					if (bullets[collidedBulletIndex].y < meshPart.Object3D.y)
						impY = MESH_SIZE*SHOOT_POWER*.5
					else
						impY = -MESH_SIZE*SHOOT_POWER*.5
						
					meshPart.LinImpulse = new Number3D(impX, impY, (MESH_SIZE - distance)*SHOOT_POWER)
					meshPart.RotImpulse= new Number3D(Math.random()*.6,Math.random()*.6,Math.random()*.6)
				}

			}

			isPhysicsOn = true;
		}
		scene.removeChild(mesh)
	}
	
	public function restoreMesh():void 
	{
		if (meshPartsPhysics.length > 0) {
			for (var i:int = 0; i < meshPartsPhysics.length; i++) 
			{
				scene.removeChild(meshPartsPhysics[i].Object3D);
			}
		}
		mesh = new Plane(meshMaterial, MESH_SIZE, MESH_SIZE, MESH_TESSELATION_X, MESH_TESSELATION_Y)
		scene.addChild(mesh)
		isPhysicsOn = false
		brokenCenterBmp.alpha = 0
	}

	public function enterFrameHandler(event:Event):void {
		if(isPhysicsOn){
			updatePhysics();
			detainedPartsCounter++
		}
		updateBullet();
		rotateCamera();
		singleRender();
	}
	
	private function updateBullet():void 
	{
		for each(var bullet:DisplayObject3D in bullets) {
			bullet.moveForward(100);
			if(bullet.hitTestObject(mesh)){
				breakMesh(mesh);
				collidedBulletIndex = bullets.indexOf(bullet)
			}
			if (Math.abs(bullet.x) > BOUNDS/2 || Math.abs(bullet.y) > BOUNDS/2 || Math.abs(bullet.z) > BOUNDS/2) {
				bullets.splice(bullets.indexOf(bullet), 1 );
				scene.removeChild(bullet);
			}
		}
	}
	
	private function updatePhysics():void 
	{
		for (var i:int = 0; i < meshPartsPhysics.length; i++) 
		{
			if(meshPartsPhysics[i].isDetained == false || detainedPartsCounter > meshPartsPhysics.length*SHOOT_POWER){
				if (meshPartsPhysics[i].Object3D.y > GROUND_Y) {
						meshPartsPhysics[i].AddForce(meshPartsPhysics[i].CGPosition, Number3DPlus.multiply(new Number3D(0, -.2, 0), meshPartsPhysics[i].Mass));
						meshPartsPhysics[i].Update(1.7);	
				}else {
					if(meshPartsPhysics[i].isStopped == false){
						meshPartsPhysics[i].resetPos();
						meshPartsPhysics[i].Object3D.rotationY = Math.random()*180 // dirty fix
						meshPartsPhysics[i].ResetImpulse();
						meshPartsPhysics[i].ResetForce();
						meshPartsPhysics[i].Object3D.y = GROUND_Y
						meshPartsPhysics[i].Object3D.rotationX = 90
						meshPartsPhysics[i].Object3D.rotationZ = 0
						meshPartsPhysics[i].isStopped = true
					}
				}
			}
		}	
	}
	
	public function addListeners():void {
		addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
		stage.addEventListener(MouseEvent.CLICK, onMouseClick);
	}
	
	private function rotateCamera():void
	{
		camera.rotationX = -(viewport.mouseY - stage.height / 2) / 5;
		camera.rotationY = (viewport.mouseX - stage.width / 2) / 5;
	}
	
	private function onMouseClick(e:MouseEvent):void {
		var bullet:Plane = new Plane(new BitmapFileMaterial(BULLET_TEXTURE), 64, 64, 1, 1)
		bullet.copyPosition(camera);
		bullet.copyTransform(camera);
		bullet.moveForward(400);
		scene.addChild(bullet);
		bullets.push(bullet);
	}
		
	public function keyUpHandler(e:KeyboardEvent):void {
		if (isChar(e.charCode, "r")) {
			if (meshPartsPhysics.length > 0)
				restoreMesh();
			else
				breakMesh(mesh);
		}
	}
	
	private function isChar(code:Number, str:String):Boolean{
		if(code == str.charCodeAt())
			return true;
		return false;
	}
}
}